package com.xiaoshuidi.cloud.framework.pay.core.client.impl.tongshangyun;

import cn.hutool.core.date.DateUtil;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.allinpay.sdk.OpenClient;
import com.allinpay.sdk.bean.BizParameter;
import com.allinpay.sdk.bean.OpenConfig;
import com.allinpay.sdk.bean.OpenResponse;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.xiaoshuidi.cloud.framework.common.exception.ErrorCode;
import com.xiaoshuidi.cloud.framework.common.exception.ServiceException;
import com.xiaoshuidi.cloud.framework.common.exception.enums.GlobalErrorCodeConstants;
import com.xiaoshuidi.cloud.framework.common.util.json.JsonUtils;
import com.xiaoshuidi.cloud.framework.pay.core.client.AbstractPayCodeMapping;
import com.xiaoshuidi.cloud.framework.pay.core.client.PayCommonResult;
import com.xiaoshuidi.cloud.framework.pay.core.client.dto.PayOrderUnifiedReqDTO;
import com.xiaoshuidi.cloud.framework.pay.core.client.dto.PayOrderUnifiedRespDTO;
import com.xiaoshuidi.cloud.framework.pay.core.client.dto.PayQueryUnifiedRespDTO;
import com.xiaoshuidi.cloud.framework.pay.core.client.impl.AbstractPayClient;
import com.xiaoshuidi.cloud.framework.pay.core.client.impl.tongshangyun.bean.BankCard;
import com.xiaoshuidi.cloud.framework.pay.core.client.impl.tongshangyun.bean.DivideRule;
import com.xiaoshuidi.cloud.framework.pay.core.client.impl.tongshangyun.dto.EnterpriseAccountEstablishReqDTO;
import com.xiaoshuidi.cloud.framework.pay.core.client.impl.tongshangyun.dto.EnterpriseAccountEstablishRespDTO;
import com.xiaoshuidi.cloud.framework.pay.core.client.impl.tongshangyun.dto.MemberBindReqDTO;
import com.xiaoshuidi.cloud.framework.pay.core.client.impl.tongshangyun.dto.MemberBindRespDTO;
import com.xiaoshuidi.cloud.framework.pay.core.client.impl.tongshangyun.dto.MemberCreateReqDTO;
import com.xiaoshuidi.cloud.framework.pay.core.client.impl.tongshangyun.dto.MemberCreateRespDTO;
import com.xiaoshuidi.cloud.framework.pay.core.client.impl.tongshangyun.dto.QueryBalanceReqDTO;
import com.xiaoshuidi.cloud.framework.pay.core.client.impl.tongshangyun.dto.QueryBalanceRespDTO;
import com.xiaoshuidi.cloud.framework.pay.core.client.impl.tongshangyun.dto.WithdrawApplyReqDTO;
import com.xiaoshuidi.cloud.framework.pay.core.client.impl.tongshangyun.dto.WithdrawApplyRespDTO;
import com.xiaoshuidi.cloud.framework.pay.core.client.impl.tongshangyun.enums.OrderStatusEnum;
import com.xiaoshuidi.cloud.framework.pay.core.client.impl.tongshangyun.enums.TongshangyunApiType;
import com.xiaoshuidi.cloud.framework.pay.core.client.impl.tongshangyun.enums.TongshangyunValidateTypeEnum;
import com.xiaoshuidi.cloud.framework.pay.core.enums.PayDisplayModeEnum;
import com.xiaoshuidi.cloud.framework.pay.core.enums.PayStatusEnum;
import lombok.extern.slf4j.Slf4j;
import org.springframework.util.CollectionUtils;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;

import java.math.BigDecimal;
import java.time.format.DateTimeFormatter;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import static com.xiaoshuidi.cloud.framework.common.util.json.JsonUtils.toJsonString;
import static com.xiaoshuidi.cloud.framework.pay.core.client.impl.tongshangyun.enums.TongshangyunApiType.*;

/**
 * 通商云抽象类
 */

@Slf4j
public abstract class AbstractTongshangyunPayClient extends AbstractPayClient<TongshangyunClientConfig> {

    protected OpenClient client;


    public AbstractTongshangyunPayClient(Long channelId, String channelCode, TongshangyunClientConfig config, AbstractPayCodeMapping codeMapping) {
        super(channelId, channelCode, config, codeMapping);
    }

    @Override
    protected void doInit() {
        TongshangyunClientConfig conf = config;
        OpenConfig oc = new OpenConfig(conf.getUrl(), config.getAppId(), conf.getSecretKey(), conf.getCertPath(), conf.getCertPwd(), conf.getTlCertPath());
        this.client = new OpenClient(oc);
    }

    protected BizParameter baseParams(PayOrderUnifiedReqDTO reqDTO) {
        BizParameter param = new BizParameter();
        param.put("payerId", reqDTO.getPayerId());
        param.put("recieverId", reqDTO.getRecieverId());
        param.put("bizOrderNo", reqDTO.getMerchantOrderId());
        param.put("amount", reqDTO.getAmount());
        param.put("fee", reqDTO.getFee());
        param.put("validateType", TongshangyunValidateTypeEnum.MESSAGE.getStatus());
// *** split rule
        if (reqDTO.getDivideFlag().booleanValue()) {
            DivideRule divideRule = reqDTO.getDivideRule();
            JSONArray splitRule = new JSONArray();
            if (!CollectionUtils.isEmpty(divideRule.getRuleList())) {
                for (DivideRule rule : divideRule.getRuleList()) {
                    HashMap<String, Object> splitRule1 = new HashMap<>();
                    splitRule1.put("bizUserId", rule.getBizUserId());
                    splitRule1.put("amount", rule.getAmount());
                    splitRule1.put("fee", rule.getFee());
                    splitRule1.put("remark", rule.getRemark());
                    splitRule.add(new JSONObject(splitRule1));
                }
            }
            param.put("splitRule", splitRule);
        }
        param.put("frontUrl", reqDTO.getFrontUrl());
        param.put("backUrl", reqDTO.getNotifyUrl());

        String orderExpireDatetime = reqDTO.getExpireTime().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
        param.put("orderExpireDatetime", orderExpireDatetime);
        param.put("goodsName", reqDTO.getSubject());
        param.put("goodsDesc", reqDTO.getBody());
        param.put("industryCode", "1613");//房产租赁
        param.put("industryName", "房产租赁");
        param.put("source", 1L);//1Mobile 2PC
        param.put("summary", reqDTO.getMerchantOrderId() + "订单");
//        param.put("extendInfo", reqDTO.getChannelExtras());
        return param;
    }

    protected <T> PayCommonResult<T> call(String api, BizParameter param, String merchantOrderId) {
        TongshangyunApiType enumByApi = getEnumByApi(api);
        try {
            OpenResponse response = client.execute(api, param);
            log.info("通商云结果: [{}] {}", api, JSONObject.toJSONString(response));
            String errCode = StringUtils.hasText(response.getData()) ? response.getCode() : "40004";
            String errMsg = StringUtils.hasText(response.getData()) ? response.getMsg() : response.getSubMsg();
            ErrorCode code = codeMapping.apply(errCode, errMsg);
            switch (enumByApi) {
                case CONSUME_APPLY:
                    PayOrderUnifiedRespDTO<ObjectNode> r = new PayOrderUnifiedRespDTO<>();
                    if ("OK".equals(response.getSubCode())) {
                        String data = response.getData().replace("\\", "");
                        HashMap resultMap = JsonUtils.parseObject(data, HashMap.class);
                        r.setMerchantOrderId(resultMap.get("orderNo").toString())
                                .setDisplayMode(PayDisplayModeEnum.APP.getMode())
                                .setDisplayContent(code == GlobalErrorCodeConstants.SUCCESS ? response.getMsg() : null)
                                .setRawResponse(getObjectNode(data))
                                .setRawRequest(JsonUtils.toJsonString(param));
                        return (PayCommonResult<T>) PayCommonResult.build(errCode, errMsg, r, codeMapping);
                    }
                    throw new ServiceException(code);
                case GET_ORDER_DETAIL:
                    PayQueryUnifiedRespDTO payQueryUnifiedRespDTO = new PayQueryUnifiedRespDTO();
                    if ("OK".equals(response.getSubCode())) {
                        String data = response.getData();
                        HashMap resultMap = JsonUtils.parseObject(data, HashMap.class);
                        String status = resultMap.get("orderStatus").toString();
                        OrderStatusEnum statusEnum = OrderStatusEnum.getEnumByStatus(status);
                        switch (statusEnum) {
                            case TRANSACTION_SUCCESS:
                            case TRANSACTION_SUCCESS_REFUND:
                            case TRANSACTION_SUCCESS_RETRUN_TICKET:
                                payQueryUnifiedRespDTO.setPayAmount(new BigDecimal(resultMap.get("amount").toString()))
                                        .setTransactionId(resultMap.get("orderNo").toString())
                                        .setMerchantOrderId(merchantOrderId)
                                        .setStatus(PayStatusEnum.SUCCESS)
                                        .setPayTime(DateUtil.parseLocalDateTime(resultMap.get("payDatetime").toString()));
                                return (PayCommonResult<T>) PayCommonResult.build(errCode, errMsg, payQueryUnifiedRespDTO, codeMapping);
                            case CLOSED:
                                payQueryUnifiedRespDTO.setStatus(PayStatusEnum.CLOSED);
                                break;
                            default:
                                payQueryUnifiedRespDTO.setStatus(PayStatusEnum.WAITING);
                        }
                        return (PayCommonResult<T>) PayCommonResult.build(errCode, errMsg, payQueryUnifiedRespDTO, codeMapping);
                    }
                    throw new ServiceException(code);
                case CREATE_MEMBER:
                    MemberCreateRespDTO memberCreateRespDTO = new MemberCreateRespDTO();
                    if ("OK".equals(response.getSubCode())) {
                        String data = response.getData();
                        HashMap resultMap = JsonUtils.parseObject(data, HashMap.class);
                        memberCreateRespDTO.setUserId(resultMap.get("userId").toString());
                        memberCreateRespDTO.setBizUserId(resultMap.get("bizUserId").toString());
                        return (PayCommonResult<T>) PayCommonResult.build(errCode, errMsg, memberCreateRespDTO, codeMapping);
                    }
                    throw new ServiceException(code);
                case APPLY_BIND_ACCT:
                    MemberBindRespDTO memberBindRespDTO = new MemberBindRespDTO();
                    if ("OK".equals(response.getSubCode())) {
                        String data = response.getData();
                        HashMap resultMap = JsonUtils.parseObject(data, HashMap.class);
                        memberBindRespDTO.setBizUserId(resultMap.get("bizUserId").toString());
                        memberBindRespDTO.setResult(resultMap.get("result").toString());
                        return (PayCommonResult<T>) PayCommonResult.build(errCode, errMsg, memberBindRespDTO, codeMapping);
                    }
                    throw new ServiceException(code);
                case ENTERPRISE_MEMBER_ACCOUNT_ESTABLISH:
                    EnterpriseAccountEstablishRespDTO establishRespDTO = new EnterpriseAccountEstablishRespDTO();
                    if ("OK".equals(response.getSubCode())) {
                        String data = response.getData();
                        HashMap resultMap = JsonUtils.parseObject(data, HashMap.class);
                        establishRespDTO.setBizUserId(resultMap.get("bizUserId").toString());
                        establishRespDTO.setUserId(resultMap.get("userId").toString());
                        establishRespDTO.setRegInviteLink(resultMap.get("regInviteLink").toString());
                        establishRespDTO.setRegInviteLinkEndTime(resultMap.get("regInviteLinkEndTime").toString());

                        return (PayCommonResult<T>) PayCommonResult.build(errCode, errMsg, establishRespDTO, codeMapping);
                    }
                    throw new ServiceException(code);
                case QUERY_BANK_CARD:
                    if ("OK".equals(response.getSubCode())) {
                        String data = response.getData();
                        HashMap resultMap = JsonUtils.parseObject(data, HashMap.class);
                        List<BankCard> bindCardList = (List<BankCard>) resultMap.get("bindCardList");
                        if (!ObjectUtils.isEmpty(bindCardList) && bindCardList.size() > 0) {
                            String bankCardNo = client.decrypt(((Map) bindCardList.get(0)).get("bankCardNo").toString());
                            return (PayCommonResult<T>) PayCommonResult.build(errCode, errMsg, bankCardNo, codeMapping);
                        }
                        return (PayCommonResult<T>) PayCommonResult.build(errCode, errMsg, "", codeMapping);
                    }
                    throw new ServiceException(code);
                case QUERY_BALANCE:
                    QueryBalanceRespDTO queryBalanceRespDTO = new QueryBalanceRespDTO();
                    if ("OK".equals(response.getSubCode())) {
                        String data = response.getData();
                        HashMap resultMap = JsonUtils.parseObject(data, HashMap.class);
                        queryBalanceRespDTO.setAllAmount(Long.parseLong(resultMap.get("allAmount").toString()));
                        queryBalanceRespDTO.setFreezenAmount(Long.parseLong(resultMap.get("freezenAmount").toString()));
                        queryBalanceRespDTO.setDepositFreezenAmount(Long.parseLong(resultMap.get("depositFreezenAmount").toString()));
                        return (PayCommonResult<T>) PayCommonResult.build(errCode, errMsg, queryBalanceRespDTO, codeMapping);
                    }
                    throw new ServiceException(code);
                case WITHDRAW_APPLY:
                    WithdrawApplyRespDTO withdrawApplyRespDTO = new WithdrawApplyRespDTO();
                    if ("OK".equals(response.getSubCode())) {
                        String data = response.getData();
                        HashMap resultMap = JsonUtils.parseObject(data, HashMap.class);
                        withdrawApplyRespDTO.setOrderNo(resultMap.get("orderNo").toString());
                        withdrawApplyRespDTO.setBizOrderNo(resultMap.get("bizOrderNo").toString());
                        //todochannelExtendInfo  渠道扩展字段
                        //针对通联通协议支支付-QUICKPAY_TLT、实名付（单笔）- REALNAMEPAY、通联通代付-WITHDRAW_TLT，返回银行流水号字段
                        withdrawApplyRespDTO.setExtendInfo(resultMap.get("extendInfo").toString());
                        return (PayCommonResult<T>) PayCommonResult.build(errCode, errMsg, withdrawApplyRespDTO, codeMapping);
                    }
                    throw new ServiceException(code);
                default:
                    return null;
            }

        } catch (ServiceException e) {
            return PayCommonResult.build(e.getCode(), e.getMessage());
        } catch (Exception e) {
            switch (enumByApi) {
                case CONSUME_APPLY:
                    log.error("[unifiedOrder][{}][request({}) 通商云发起支付失败]", api, toJsonString(param), e);
                case GET_ORDER_DETAIL:
                    log.error("[getOrderDetail][{}][request({})订单状态查询失败]", api, toJsonString(param), e);
                case CREATE_MEMBER:
                    log.error("[createMember][{}][request({})创建用户失败]", api, toJsonString(param), e);
                case APPLY_BIND_ACCT:
                    log.error("[applyBindAcct][{}][request({})会员绑定支付账户用户标识失败]", api, toJsonString(param), e);
                default:
                    log.error("[unifiedOrder][{}][request({})通商云查询失败]", api, toJsonString(param), e);
            }

            return PayCommonResult.build("ERROR", "通商云下单请求异常", null, codeMapping);
        }
    }

    protected ObjectNode getObjectNode(Object obj) {
        ObjectMapper mapper = new ObjectMapper();
        mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
        mapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false);
        ObjectNode tree = mapper.valueToTree(obj);
        return tree;
    }

    protected ObjectNode getObjectNode(String strJson) throws JsonProcessingException {
        ObjectMapper mapper = new ObjectMapper();
        mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
        mapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false);
        JsonNode jsonNode = mapper.readTree(strJson);
        ObjectNode objectNode = null;
        if (jsonNode instanceof ObjectNode) {
            objectNode = (ObjectNode) jsonNode;
        }
        return objectNode;
    }

    /**
     * 创建用户
     *
     * @param reqDTO
     * @return
     */
    @Override
    public PayCommonResult<MemberCreateRespDTO> createMember(MemberCreateReqDTO reqDTO) {
        PayCommonResult<MemberCreateRespDTO> resp;
        try {
            resp = doCreateMember(reqDTO);
        } catch (Throwable ex) {
            // 记录异常日志
            log.error("[createMember][request({}) 创建用户失败]", toJsonString(reqDTO), ex);
            resp = PayCommonResult.error(ex);
        }

        return resp;
    }

    protected PayCommonResult<MemberCreateRespDTO> doCreateMember(MemberCreateReqDTO reqDTO) {
        BizParameter param = new BizParameter();
        param.put("bizUserId", reqDTO.getBizUserId());
        param.put("memberType", reqDTO.getMemberType());
        param.put("source", reqDTO.getSource());//1Mobile 2PC
        log.info("通商云创建用户请求数据：{}", param);
        return call(CREATE_MEMBER.getApi(), param, null);
    }


    @Override
    public PayCommonResult<MemberBindRespDTO> memberBind(MemberBindReqDTO reqDTO) {
        PayCommonResult<MemberBindRespDTO> resp;
        try {
            resp = doMemberBind(reqDTO);
        } catch (Throwable ex) {
            // 记录异常日志
            log.error("[memberBind][request({}) 会员绑定支付账户用户标识失败]", toJsonString(reqDTO), ex);
            resp = PayCommonResult.error(ex);
        }

        return resp;
    }

    protected PayCommonResult<MemberBindRespDTO> doMemberBind(MemberBindReqDTO reqDTO) {
        final BizParameter param = new BizParameter();
        param.put("bizUserId", reqDTO.getBizUserId());
        param.put("operationType", reqDTO.getOperationType());
        param.put("acctType", reqDTO.getAcctType());
        param.put("acct", reqDTO.getAcct());
        log.info("通商云会员绑定支付账户用户标识请求数据：{}", param);
        return call(APPLY_BIND_ACCT.getApi(), param, null);
    }

    @Override
    public PayCommonResult<EnterpriseAccountEstablishRespDTO> establishEnterpriseAccount(EnterpriseAccountEstablishReqDTO reqDTO) {
        PayCommonResult<EnterpriseAccountEstablishRespDTO> resp;
        try {
            resp = doEstablishEnterpriseAccount(reqDTO);
        } catch (Throwable ex) {
            // 记录异常日志
            log.error("[establishEnterpriseAccount][request({}) 企业会员开户H5失败]", toJsonString(reqDTO), ex);
            resp = PayCommonResult.error(ex);
        }

        return resp;
    }

    protected PayCommonResult<EnterpriseAccountEstablishRespDTO> doEstablishEnterpriseAccount(EnterpriseAccountEstablishReqDTO reqDTO) {
        final BizParameter param = new BizParameter();
        param.put("bizUserId", reqDTO.getBizUserId());
        param.put("companyName", reqDTO.getCompanyName());
//        param.put("companyAddress", reqDTO.getCompanyAddress());
//        param.put("comproperty", reqDTO.getComproperty());
//        param.put("authType", reqDTO.getAuthType());
//        param.put("uniCredit", reqDTO.getUniCredit());
//        param.put("phone", reqDTO.getPhone());
//        param.put("legalName", reqDTO.getLegalName());
//        param.put("legalIds", client.encrypt(reqDTO.getLegalIds()));
//        param.put("identityType", reqDTO.getIdentityType());
        param.put("accountNo", client.encrypt(reqDTO.getAccountNo()));
        param.put("backUrl", reqDTO.getBackUrl());
        param.put("jumpUrl", reqDTO.getJumpUrl());
        log.info("通商云企业会员开户H5请求数据：{}", param);
        return call(ENTERPRISE_MEMBER_ACCOUNT_ESTABLISH.getApi(), param, null);
    }

    @Override
    public PayCommonResult<QueryBalanceRespDTO> queryBalance(QueryBalanceReqDTO reqDTO) {
        PayCommonResult<QueryBalanceRespDTO> resp = null;

        try {
            resp = doQueryBalance(reqDTO);
        } catch (Throwable ex) {
            // 记录异常日志
            log.error("[queryBalance][request({}) 查询余额失败]", toJsonString(reqDTO), ex);
            resp = PayCommonResult.error(ex);
        }

        return resp;
    }

    private PayCommonResult<QueryBalanceRespDTO> doQueryBalance(QueryBalanceReqDTO reqDTO) {
        BizParameter param = new BizParameter();
        param.put("bizUserId", reqDTO.getBizUserId());
        param.put("accountSetNo", config.getAccountSetNo());
        log.info("通商云查询余额请求数据：{}", param);
        return call(QUERY_BALANCE.getApi(), param, null);
    }

    @Override
    public PayCommonResult<WithdrawApplyRespDTO> withdrawApply(WithdrawApplyReqDTO reqDTO) {
        PayCommonResult<WithdrawApplyRespDTO> resp = null;

        try {
            resp = doWithdrawApply(reqDTO);
        } catch (Throwable ex) {
            // 记录异常日志
            log.error("[withdrawApply][request({}) 提现申请失败]", toJsonString(reqDTO), ex);
            resp = PayCommonResult.error(ex);
        }

        return resp;
    }

    private PayCommonResult<WithdrawApplyRespDTO> doWithdrawApply(WithdrawApplyReqDTO reqDTO) {
        BizParameter param = new BizParameter();
        param.put("bizOrderNo", reqDTO.getBizOrderNo());
        param.put("bizUserId", reqDTO.getBizUserId());
        param.put("accountSetNo", config.getAccountSetNo());
        param.put("amount", reqDTO.getAmount());
        param.put("fee", reqDTO.getFee());
        //param.put("validateType",);//默认
        param.put("backUrl", reqDTO.getBackUrl());
        param.put("orderExpireDatetime", reqDTO.getOrderExpireDatetime());
        // param.put("payMethod",);//则默认为通联通代付
        param.put("bankCardNo", client.encrypt(reqDTO.getBankCardNo()));
        param.put("bankCardPro", reqDTO.getBankCardPro());
//        param.put("withdrawType", "T1customized");
        param.put("withdrawType", "D0");
        param.put("validateType", 0L);
        param.put("industryCode", "1613");//房产租赁
        param.put("industryName", "房产租赁");
        param.put("source", 2L);//1Mobile 2PC
        param.put("summary", reqDTO.getBizOrderNo() + "提现");
        param.put("extendInfo", "this is extendInfo");
        log.info("通商云提现请求数据：{}", param);
        return call(WITHDRAW_APPLY.getApi(), param, null);
    }

    @Override
    public PayCommonResult<String> queryBankCard(String bizUserId) {
        PayCommonResult<String> resp = null;

        try {
            resp = doQueryBankCard(bizUserId);
        } catch (Throwable ex) {
            // 记录异常日志
            log.error("[queryBankNo][request({}) 提现申请失败]", bizUserId, ex);
            resp = PayCommonResult.error(ex);
        }

        return resp;
    }

    private PayCommonResult<String> doQueryBankCard(String bizUserId) {
        BizParameter param = new BizParameter();
        param.put("bizUserId", bizUserId);
        log.info("通商云查询银行卡请求数据：{}", param);
        return call(QUERY_BANK_CARD.getApi(), param, null);
    }
}
